#include "serialport_engine.h"

#include <QDebug>
#include <QSerialPortInfo>

static const int baudrate_list[16] = {
    300,     600,   1200,   2400,
    4800,    9600,  14400,  19200,
    38400,   56000,  57600, 115200,
    128000, 256000, 460800, 512000
};

static const int data_bit_list[4] = {
    QSerialPort::Data5,
    QSerialPort::Data6,
    QSerialPort::Data7,
    QSerialPort::Data8,
};

static const int parity_list[4] = {
    QSerialPort::NoParity,
    QSerialPort::EvenParity,
    QSerialPort::MarkParity,
    QSerialPort::OddParity,
};

static const int stop_bit_list[3] = {
    QSerialPort::OneStop,
    QSerialPort::OneAndHalfStop,
    QSerialPort::TwoStop,
};

static const int flow_control[3] = {
    QSerialPort::NoFlowControl,
    QSerialPort::HardwareControl,
    QSerialPort::SoftwareControl,
};

SerialPortEngine::SerialPortEngine() {
    serialPort = new QSerialPort();
    timer = new QTimer();
    connect(serialPort, &QSerialPort::readyRead, this, &SerialPortEngine::handleReadyRead);
    connect(timer, &QTimer::timeout, this,  &SerialPortEngine::handleTimeout);
    timer->setSingleShot(false);
    timer->setInterval(1);
    initialized = true;
}

SerialPortEngine::~SerialPortEngine() {
    delete serialPort;
    delete timer;
    initialized = false;
}

int SerialPortEngine::connectDevice(const QString &name) {
    if(!initialized) {return ErrorCode::OBJECT_IS_NULL;} // 对象为空
    if(!opened) {
        serialPort->setPortName(name);
        bool success = serialPort->open(QIODevice::ReadWrite);
        if(success) {
            opened = true;
            return ErrorCode::SERIAL_SUCCESS; // 打开成功
        }
        else {
            return ErrorCode::SERIAL_IS_OCCUPIED; // 设备被别的资源占用，或者不存在
        }
    }
    else {
        return ErrorCode::SERIAL_IS_CONNECTED; // 已经打开了
    }
}

void SerialPortEngine::disConnectDevice() {
    if(!initialized) {return;}
    serialPort->close();
    opened = false;
}

void SerialPortEngine::receiveBytesConfirmation() {
    confirm_count++;
}

void SerialPortEngine::setBreakType(BreakTypeType type) {
    if(type > BreakTypeType::BREAK_LF || type < BreakTypeType::BREAK_INTERVAL) {return;}
    if(type > BreakTypeType::BREAK_INTERVAL) {
        if(timer->isActive()) {
            timer->stop();
        }
    }
    else {
        timer->start();
    }
    bytes_buffer.clear();
    frame_list.clear();
    break_type = type;
}

void SerialPortEngine::scanExistenceDevice() {
    const auto serialPortInfos = QSerialPortInfo::availablePorts();
    serial_info_list.clear();
    for (const QSerialPortInfo &serialPortInfo : serialPortInfos) {
        SerialPortInfo info;
        info.port = serialPortInfo.portName();
        info.location = serialPortInfo.systemLocation();
        info.description = serialPortInfo.description();
        info.manufacturer = serialPortInfo.manufacturer();
        info.serialNumber = serialPortInfo.serialNumber();
        info.vendor_id = serialPortInfo.vendorIdentifier();
        info.product_id = serialPortInfo.productIdentifier();
        serial_info_list.append(info);
    }
}

QStringList SerialPortEngine::nameList() const {
    QStringList names;
    for(int i = 0; i < serial_info_list.size(); i++) {
        SerialPortInfo info = serial_info_list.at(i);
        names.append(info.port);
    }
    return names;
}

void SerialPortEngine::updateSettings(const int baudRate, const int dataBits, const int parity, const int stopBits) {
    if(!initialized) {return;}
    if(!opened) {
        if(baudRate >= 0 && baudRate < 16) {
            serialPort->setBaudRate(baudrate_list[baudRate]);
        }

        if(dataBits >= 0 && dataBits < 4) {
            serialPort->setDataBits(QSerialPort::DataBits(data_bit_list[dataBits]));
        }

        if(parity >= 0 && parity < 4) {
            serialPort->setParity(QSerialPort::Parity(parity_list[parity]));
        }

        if(stopBits >= 0 && stopBits < 3) {
            serialPort->setStopBits(QSerialPort::StopBits(stop_bit_list[stopBits]));
        }
    }
    else {
        // TODO: report ERROR
    }
}

void SerialPortEngine::setFlowControl(const int flowCtrl) {
    if(!initialized) {return;}
    if(!opened) {
        if(flowCtrl >= 0 && flowCtrl < 3) {
            serialPort->setFlowControl(QSerialPort::FlowControl(flow_control[flowCtrl]));
        }
    }
}

void SerialPortEngine::sendMessage(const PduInfoType *pdu) {
    int ret = serialPort->write((const char*)pdu->SduDataPtr, pdu->SduLength);
}

void SerialPortEngine::handleReadyRead() {
    timer_ticks = 0;
    QByteArray bytes = serialPort->readAll();
    if((bytes_buffer.size() + bytes.size()) >= frame_buffer_deepth) {return;}   // clip
    bytes_buffer.append(bytes);

    if(break_type == BreakTypeType::BREAK_CRLF) {
        int bytes_count = bytes_buffer.size();
        if(bytes_count > 1) {
            int start_p = 0;
            for(int i = 1; i < bytes_count; i++) {
                if((bytes_buffer.at(i) == 0x0a) && (bytes_buffer.at(i-1) == 0x0d)) {
                    QByteArray frame = bytes_buffer.mid(start_p, i-1-start_p);
                    if(frame.size() > 0) {
                        frame_list.append(frame);
                    }
                    start_p += (frame.size() + 2);
                }
            }
            if(start_p > 0) {
                bytes_buffer.remove(0, start_p);
            }
        }
        frameListIndication();
    }
}

void SerialPortEngine::handleTimeout() {
    if(timer_ticks < frame_interval) {
        timer_ticks++;
    }
    else {
        int bytes_count = bytes_buffer.size();
        if(bytes_count > 0) {
            int frame_count = frame_list.size();
            if(frame_count >= frame_list_deepth) {
                frame_list.removeFirst();                           // fifo give up the oldest frame
            }
            frame_list.append(bytes_buffer);                        // add to list
            bytes_buffer.clear();    
            frameListIndication();                                  // clear temp
        }        
    }
}

void SerialPortEngine::frameListIndication() {
    if(confirm_count >= frame_list.size()) {
        frame_list.clear();
    }
    else {
        frame_list.remove(0, confirm_count);
    }
    confirm_count = 0;

    for(int i = 0; i < frame_list.size(); i++) {
        PduInfoType pdu;
        pdu.SduDataPtr = (unsigned char*)frame_list.at(i).data();
        pdu.SduLength = frame_list.at(i).size();
        emit receiveBytesIndication(&pdu);                  // tell up layer
    }
}
